package com.itheima.pinda.file.storage;

import cn.hutool.core.util.StrUtil;
import com.itheima.pinda.base.R;
import com.itheima.pinda.file.domain.FileDeleteDO;
import com.itheima.pinda.file.dto.chunk.FileChunksMergeDTO;
import com.itheima.pinda.file.entity.File;
import com.itheima.pinda.file.properties.FileServerProperties;
import com.itheima.pinda.file.strategy.impl.AbstractFileChunkStrategy;
import com.itheima.pinda.file.strategy.impl.AbstractFileStrategy;
import com.itheima.pinda.utils.DateUtils;
import com.itheima.pinda.utils.StrPool;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.nio.channels.FileChannel;
import java.nio.file.Paths;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.UUID;

/**
 * Created with IntelliJ IDEA.
 *
 * @Author: 吴向明
 * @Date: 2021/09/01/23:29
 * @Description:本地上传策略配置类
 */
@Configuration
@Slf4j
//启用配置属性类
@EnableConfigurationProperties(FileServerProperties.class)
/*
 当远程的配置属性是"LOCAL"的时候,havingValue判断生效,如果为生效会创建一个LocalServiceImpl的bean对象交给spring管理
 */
@ConditionalOnProperty(name = "pinda.file.type", havingValue = "LOCAL")
public class LocalAutoConfigure {

    /**
     * 本地文件策略处理类
     */
    @Service
    public class LocalServiceImpl extends AbstractFileStrategy {

        private void buildClient() {
            properties = fileProperties.getLocal();
        }

        /**
         * 文件上传
         *
         * @param file          文件信息
         * @param multipartFile 文件本身
         * @return
         */
        @Override
        public void uploadFile(File file, MultipartFile multipartFile) throws Exception {
            buildClient();
            String endpoint = properties.getEndpoint();
            String bucketName = properties.getBucketName();
            String uriPrefix = properties.getUriPrefix();

            //使用UUID为文件生成新文件名
            String fileName = UUID.randomUUID().toString() + StrPool.DOT + file.getExt();

            //使用UUID为文件生成新文件名
            //日期目录的相对路径
            String relativePath = Paths.get(LocalDate.now().format(DateTimeFormatter.ofPattern(DateUtils.DEFAULT_MONTH_FORMAT_SLASH))).toString();

            //上传文件存储的绝对目录
            String absolutePath = Paths.get(endpoint, bucketName, relativePath).toString();

            java.io.File outFile = new java.io.File(Paths.get(absolutePath, fileName).toString());

            //向目标文件写入数据
            FileUtils.writeByteArrayToFile(outFile, multipartFile.getBytes());

            //文件上传完成后需要设置File对象的属性(url,filename,relativepath)用于保存数据库
            String url = new StringBuilder(getUriPrefix())
                    .append(StrPool.SLASH)
                    .append(properties.getBucketName())
                    .append(StrPool.SLASH)
                    .append(relativePath)
                    .append(StrPool.SLASH)
                    .append(fileName)
                    .toString();
            //替换掉windows环境的\路径
            url = StrUtil.replace(url, "\\\\", StrPool.SLASH);
            url = StrUtil.replace(url, "\\", StrPool.SLASH);
//                                    UriPrefix    /   BucketName  / relativePath /  fileName
            file.setUrl(url);  //   http://ip:port/oss-file-service/2020/05/xxx.doc
            file.setFilename(fileName);
            file.setRelativePath(relativePath);
        }

        /**
         * 文件删除
         *
         * @param fileDeleteDO
         */
        @Override
        public void delete(FileDeleteDO fileDeleteDO) {
            //拼接要删除的文件的绝对磁盘路径
            String filePath = Paths.get(properties.getEndpoint(), properties.getBucketName(), fileDeleteDO.getRelativePath(), fileDeleteDO.getFileName()).toString();
            java.io.File file = new java.io.File(filePath);
            FileUtils.deleteQuietly(file);
        }
    }

//    public static void main(String[] args) {
////        String format = LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM")).toString();
//        //Paths类可以非常智能地根据系统选择正斜线还是反斜线
//        String s = Paths.get(LocalDate.now().format(DateTimeFormatter.ofPattern("yyyy/MM"))).toString();
//        System.out.println(s);
//        String s1 = Paths.get("D:\\uploadFiles", "oss-file-service", s).toString();
//        System.out.println(s1);
//    }

    @Service
    public class LocalChunkServiceImpl extends AbstractFileChunkStrategy {

        @Override
        protected R<File> merge(List<java.io.File> files, String fileName, FileChunksMergeDTO fileChunksMergeDTO) throws Exception {

            //获取本地配置策略对象
            FileServerProperties.Properties properties = fileServerProperties.getLocal();

            String endpoint = properties.getEndpoint(); //D:\\uploadFiles
            String bucketName = properties.getBucketName(); //oss-file-service

            //日期层次
            String format = LocalDate.now().format(DateTimeFormatter.ofPattern(DateUtils.DEFAULT_MONTH_FORMAT_SLASH));
            String relativePath = Paths.get(format).toString();

            //合并后文件存放目录
            String path = Paths.get(endpoint, bucketName, relativePath).toString();

            java.io.File uploadFolder = new java.io.File(path);
            //判断目录是否存在,如果不存在就需要创建
            if (!uploadFolder.exists()) {
                uploadFolder.mkdirs();
            }

            //合并文件逻辑
            //创建合并后的文件对象
            java.io.File outputFile = new java.io.File(Paths.get(path, fileName).toString());
            if (!outputFile.exists()) {
                boolean newFile = outputFile.createNewFile();
                if (!newFile) {
                    log.error("文件合并失败");
                    return R.fail("文件合并失败");
                }

                try (FileChannel outChannel = new FileOutputStream(outputFile).getChannel()) {
                    for (java.io.File file : files) {//file对应就是分片文件
                        try (FileChannel inChannel = new FileInputStream(file).getChannel()) {
                            //分片合并最核心的代码
                            inChannel.transferTo(0, inChannel.size(), outChannel);
                        } catch (Exception ex) {
                            log.error("分片文件合并失败");
                            return R.fail("分片文件合并失败");
                        }
                        //删除当前分片
                        file.delete();
                    }
                } catch (Exception ex) {
                    log.error("分片文件合并失败");
                    return R.fail("分片文件合并失败");
                }
            } else {
                log.warn("文件[{}]已经存在", fileName);
            }

            //http://localhost:8080/2020/05/abc.avi
            String url = new StringBuilder(properties.getUriPrefix()).append(StrPool.SLASH).append(bucketName).append(StrPool.SLASH).append(relativePath).append(StrPool.SLASH).append(fileName).toString();

            //分片合并成功,需要封装File对象相关属性
            File file = File.builder().relativePath(relativePath).url(StringUtils.replace(url, "\\", StrPool.SLASH)).build();
            return R.success(file);
        }
    }
}
